<script lang="ts" setup>
import { useDebounceFnImmediate } from '@/utils/tools'
import { sy_Tabs_props } from '@/enum/component'

const props = withDefaults(defineProps<{
  markStyle?: string | Partial<CSSStyleDeclaration>
  direction?: 'horizontal' | 'vertical'
  duration?: string
  easing?: string
  location?: 'start' | 'center' | 'end' | 'nearest'
}>(), {
  direction: 'horizontal',
  duration: '0.3s',
  easing: 'ease-in-out',
  location: 'center'
})
provide(sy_Tabs_props, props)

const updateFn = useDebounceFnImmediate(async (els: MutationRecord[]) => {
  // 过滤mark元素
  if (els.some(el => mark.value!.contains(el.target))) return
  // 获取当前激活的tab
  const activeDom = getActiveDom()
  // 动画中时获取不到正确的位置
  if (activeDom && activeDom.getAnimations().length) return
  const _op = op.value
  const transition = `all ${props.duration} ${props.easing}`
  const rect = activeDom && activeDom.getBoundingClientRect()
  const visible = activeDom && rect && rect.width + rect.height > 0
  if (visible) {
    op.value = 1
    const update = {
      width: `${rect.width}px`,
      height: `${rect.height}px`,
      transform: `translateX(${activeDom.offsetLeft}px) translateY(${activeDom.offsetTop}px)`
    }
    if (_op === 1) {
      Object.assign(markPosition, {
        ...update,
        transition
      })
    } else {
      Object.assign(markPosition, {
        ...update,
        transition: 'none'
      })
      await nextTick()
      void mark.value!.offsetHeight // 为了触发动画
      Object.assign(markPosition, {
        opacity: 1,
        transition
      })
    }
  } else {
    if (_op === 1) {
      op.value = 0
      Object.assign(markPosition, {
        opacity: 0,
        width: 0,
        height: 0,
        transform: 'translateX(0) translateY(0)',
        transition: `
          opacity ${props.duration} ${props.easing},
          width 0s ${props.duration}, height 0s ${props.duration}, transform 0s ${props.duration}
        `
      })
    }
  }
}, 10)

const observer = new MutationObserver(updateFn)
const wrap = useTemplateRef('wrap')
const mark = useTemplateRef('mark')
const op = ref(0)
const markPosition = reactive<Partial<CSSStyleDeclaration>>({})
const getActiveDom = () => wrap.value!.querySelector<HTMLElement>(`.Tab[data-active='true']>div`)

onMounted(() => {
  setTimeout(() => {
    const target = getActiveDom()
    if (!target) return
    updateFn([{ target }] as unknown as MutationRecord[])
  })
  observer.observe(wrap.value!, {
    childList: true,
    attributes: true,
    subtree: true
  })
})
onUnmounted(() => observer.disconnect())
</script>

<template>
  <div class="Tabs" :class="props.direction">
    <div class="wrap" ref="wrap">
      <i class="mark" ref="mark">
        <i :style="[props.markStyle as any, markPosition as any]">
          <slot name="mark" />
        </i>
      </i>
      <slot />
    </div>
  </div>
</template>

<style lang="scss" scoped>
.Tabs {
  overflow: hidden;
  scrollbar-width: none;
  user-select: none;
  --Tabs-gap: 0;

  &.horizontal {
    overflow-x: auto;

    >.wrap>.mark {
      margin-right: calc(-1 * var(--Tabs-gap));
    }
  }

  &.vertical {
    overflow-y: auto;
    flex-direction: column;

    >.wrap>.mark {
      margin-bottom: calc(-1 * var(--Tabs-gap));
    }
  }

  .wrap {
    display: flex;
    gap: var(--Tabs-gap);
    position: relative;

    >* {
      flex-shrink: 0;
    }

    >.mark {
      pointer-events: none;
      position: relative;
      width: 0;
      height: 0;

      >i {
        position: absolute;
        top: 0;
        left: 0;
        opacity: 0;
      }
    }

    :deep(.Tab>div>.content) {
      transition: all v-bind(duration) v-bind(easing);
    }
  }
}
</style>
